#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>

#include "wavtrack.h"
#include "memorystream.h"

void wavtrack_create (WavTrack *track, WavChunk *beat_wav, Stream *out_stream)
{
  int off;

  track_create (&track->track);
  track->track.process_node = wavtrack_process_node;
  track->track.finalize = wavtrack_finalize;

  track->beat = beat_wav;
  track->beat_format_chunk = NULL;
  track->beat_data_chunk = NULL;

  for (off = 0; off < 8 + beat_wav->size;) {
    WavChunk *chunk = (WavChunk *)((char *)beat_wav + off);
    if (strncmp (chunk->id, "fmt ", 4) == 0) {
      track->beat_format_chunk = (FmtChunk *)chunk;
      off += 8 + chunk->size;
    }
    else if (strncmp (chunk->id, "RIFF", 4) == 0)
      off += 12;
    else if (strncmp (chunk->id, "data", 4) == 0) {
      track->beat_data_chunk = (DataChunk *)chunk;
      off += 8 + chunk->size;
    }
    else
      off += 8 + chunk->size;
  }

  assert (track->beat_format_chunk);
  assert (track->beat_data_chunk);

  wavstream_create (&track->wav_stream, out_stream,
                    track->beat_format_chunk->num_channels,
                    track->beat_format_chunk->num_samples_per_sec,
                    track->beat_format_chunk->w_bits_per_sample);
}

void wavtrack_process_node (Track *track, CTNode *node)
{
  switch (node->type) {
    case TempoElement:
      track_set_bpm (track, node->value);
      break;

    case BeatsElement:
      track_set_beats (track, node->value);
      break;

    case AccentLevelElement:
      track_set_accent_vol (track, node->value);
      break;

    case QuarterLevelElement:
      track_set_quarter_vol (track, node->value);
      break;

    case EighthLevelElement:
      track_set_eighth_vol (track, node->value);
      break;

    case SixteenthLevelElement:
      track_set_sixteenth_vol (track, node->value);
      break;

    case TripletLevelElement:
      track_set_triplet_vol (track, node->value);
      break;

    case MeasureElement:
      wavtrack_write_measures (WAVTRACK_CAST (track), node->value);
      break;

    case AccelElement:
      track_set_accel (track, node->value);
      break;

    case DecelElement:
      track_set_accel (track, -node->value);
      break;

    default:
      break;
  }
}

void wavtrack_finalize (Track *track)
{
  WavTrack *wtrack = (WavTrack *)track;

  wtrack->wav_stream.stream.close (STREAM_CAST (&wtrack->wav_stream));
}

void wavtrack_write_measures (WavTrack *track, int nmeasures)
{
  int nsubbeats = 12 * track->track.beats * nmeasures;
  WavStream *ws = &track->wav_stream;
  double accel_rate = track->track.accel / (double)nsubbeats;
  double dbpm = (double)track->track.bpm;
  int target_bpm = track->track.bpm + track->track.accel;

  int nsubbeat_samples;
  int subbeat_start;
  int i;
  for (i = 0; i < nsubbeats; ++i) {
    track_set_bpm (TRACK_CAST (track), (int)dbpm);
    subbeat_start = ws->stream.pos (STREAM_CAST (ws));
    nsubbeat_samples = (int)(5
      * track->beat_format_chunk->num_samples_per_sec / dbpm);

    if ((i % (12 * track->track.beats)) == 0 && track->track.avol > 0) {
      wavtrack_write_beat_to_stream (track, STREAM_CAST (ws),
                                     track->track.avol + track->track.qvol);
    }
    else if ((i % 12) == 0 && track->track.qvol > 0) {
      wavtrack_write_beat_to_stream (track, STREAM_CAST (ws),
                                     track->track.qvol);
    }
    else if ((i % 6) == 0 && track->track.evol > 0) {
      wavtrack_write_beat_to_stream (track, STREAM_CAST (ws),
                                     track->track.evol);
    }
    else if ((i % 3) == 0 && track->track.svol > 0) {
      wavtrack_write_beat_to_stream (track, STREAM_CAST (ws),
                                     track->track.svol);
    }
    else if ((i % 4) == 0 && track->track.tvol > 0) {
      wavtrack_write_beat_to_stream (track, STREAM_CAST (ws),
                                     track->track.tvol);
    }

    ws->stream.seek (STREAM_CAST (ws),
                     subbeat_start + nsubbeat_samples * 2, SeekSet);
    dbpm += accel_rate;
  }

  /* To eliminate float rounding */
  track_set_bpm (TRACK_CAST (track), target_bpm);
  track_set_accel (TRACK_CAST (track), 0);
}

void wavtrack_write_beat_to_stream (WavTrack *track, Stream *stream, int vol)
{
  short *audio = (short *)track->beat_data_chunk->data;
  int i;

  for (i = 0; i < track->beat_data_chunk->size / 2; ++i) {
    short t = audio[i];
    t = (short)(t * (vol / 100.0));
    stream->write (stream, &t, sizeof (short));
  }
}
